#include "glwidget.h"

#include <QGLShaderProgram>
#include <QImage>
#include <QMatrix4x4>
#include <QVector>
#include <QVector2D>
#include <QVector4D>

#include "gltexture.h"

GLWidget::GLWidget(QWidget *parent)
    : QGLWidget(parent)
{ }

GLWidget::~GLWidget()
{ }

void GLWidget::genMipMap2D(GLubyte *src, GLubyte **dst, unsigned srcWidth, unsigned srcHeight, unsigned *dstWidth, unsigned *dstHeight)
{
    unsigned x = 0, y = 0, texelSize = 3;
    *dstWidth = srcWidth / 2;
    if (*dstWidth <= 0) *dstWidth = 1;
    *dstHeight = srcHeight / 2;
    if (*dstHeight <= 0) *dstHeight = 1;
    *dst = new GLubyte[texelSize * (*dstWidth) * (*dstHeight)];
    for (y = 0; y < *dstHeight; ++y)
    {
        for (x = 0; x < *dstWidth; ++x)
        {
            int srcIndex[4];
            float r = 0.0f, g = 0.0f, b = 0.0f;
            int sample;
            // Compute the offsets for 2x2 grid of pixels in previous
            // image to perform box filter
            srcIndex[0] =
               (((y * 2) * srcWidth) + (x * 2)) * texelSize;
            srcIndex[1] =
               (((y * 2) * srcWidth) + (x * 2 + 1)) * texelSize;
            srcIndex[2] =
               ((((y * 2) + 1) * srcWidth) + (x * 2)) * texelSize;
            srcIndex[3] =
               ((((y * 2) + 1) * srcWidth) + (x * 2 + 1)) * texelSize;
            // Sum all pixels
            for ( sample = 0; sample < 4; sample++ )
            {
               r += src[srcIndex[sample]];
               g += src[srcIndex[sample] + 1];
               b += src[srcIndex[sample] + 2];
            }
            // Average results
            r /= 4.0;
            g /= 4.0;
            b /= 4.0;

            // Store resulting pixels
            (*dst)[ ( y * (*dstWidth) + x ) * texelSize] = (GLubyte)( r );
            (*dst)[ ( y * (*dstWidth) + x ) * texelSize + 1] = (GLubyte)( g );
            (*dst)[ ( y * (*dstWidth) + x ) * texelSize + 2] = (GLubyte)( b );
        }
    }
}

GLubyte *GLWidget::genCheckImage(unsigned width, unsigned height, GLsizei checkSize)
{
    unsigned x, y;
    GLubyte *pixels = new GLubyte[width * height * 3];
    for (y = 0; y < height; ++y)
    {
        for(x = 0; x < width; ++x)
        {
            GLubyte rColor = 0, bColor = 0;
            if (( x / checkSize ) % 2 == 0)
            {
                rColor = 255 * ( ( y / checkSize ) % 2 );
                bColor = 255 * ( 1 - ( ( y / checkSize ) % 2 ) );
            }
            else
            {
                bColor = 255 * ( ( y / checkSize ) % 2 );
                rColor = 255 * ( 1 - ( ( y / checkSize ) % 2 ) );
            }
            pixels[(y * height + x) * 3] = rColor;
            pixels[(y * height + x) * 3 + 1] = 0;
            pixels[(y * height + x) * 3 + 2] = bColor;
        }
    }
    return pixels;
}

void GLWidget::createMipMappedTexture2D()
{
    unsigned width = 256, height = 256, level = 0;
    GLubyte *pixels = 0, *prevImage = 0, *newImage = 0;
    pixels = genCheckImage(width, height, 8);
    GLTexture *newTexture = new GLTexture(pixels, width, height, GLTexture::FormatRGB, GLTexture::Target2D);
    _userObject.setTexture(QSharedPointer<GLTexture>(newTexture));
  //  glGenerateMipmap(GL_TEXTURE_2D);

    level = 1;
    prevImage = &pixels[0];

    while (width > 1 && height > 1)
    {
        unsigned newWidth, newHeight;

        // Generate the next mipmap level
        genMipMap2D(prevImage, &newImage, width, height, &newWidth, &newHeight);

        // Load the mipmap level
        _userObject.texture()->addMipMapLevel(level, newWidth, newHeight, newImage);

        // delete the previous image
        delete [] prevImage;
        // Set the previous image for the next iteration
        prevImage = newImage;
        ++level;
        width = newWidth;
        height = newHeight;
    }

    delete [] newImage;

    newTexture->setMagnificationFilter();
    newTexture->setMinificationFilter(GLTexture::MinificationNearestMipMapNearest);
}

void GLWidget::resizeGL(int width, int height)
{
    // nothing to do
    (void)width;
    (void)height;
}

void GLWidget::initializeGL()
{
    const char *vShaderStr =
            "uniform float u_offset;      \n"
            "attribute vec4 a_position;   \n"
            "attribute vec2 a_texCoord;   \n"
            "varying vec2 v_texCoord;     \n"
            "uniform mat4 u_mvp;          \n"
            "void main()                  \n"
            "{                            \n"
            "   gl_Position = u_mvp * a_position; \n"
            "   gl_Position.x += u_offset;\n"
            "   v_texCoord = a_texCoord;  \n"
            "}                            \n";
    const char *fShaderStr =
            "mediump float;                            \n"
            "varying vec2 v_texCoord;                            \n"
            "uniform sampler2D s_texture;                        \n"
            "void main()                                         \n"
            "{                                                   \n"
            "  gl_FragColor = texture2D( s_texture, v_texCoord );\n"
            "}                                                   \n";

    // Load the shaders and get a linked program object
    _userObject.loadProgram(vShaderStr, fShaderStr);

    // Get the attribute locations
    _userObject.setPositionLocation(_userObject.programObject()->attributeLocation("a_position"));
    _userObject.setTexCoordLocation(_userObject.programObject()->attributeLocation("a_texCoord"));

    // Get the sampler location
    _userObject.setSamplerLocation(_userObject.programObject()->uniformLocation("s_texture"));

    // Get the mvp location
    _userObject.setMvpLocation(_userObject.programObject()->uniformLocation("u_mvp"));

    // Get the offset location
    _userObject.setOffsetLocation(_userObject.programObject()->uniformLocation("u_offset"));

    // Load the texture
    createMipMappedTexture2D();

    glClearColor ( 0.0f, 0.0f, 0.0f, 1.0f );
}

void GLWidget::paintGL()
{
    QVector<QVector4D> vertexCoords;
    vertexCoords.reserve(4);
    vertexCoords
              <<    QVector4D(-0.5f, 0.5f, 0.0f, 1.5f)
              <<    QVector4D(-0.5f, -0.5f, 0.0f, 0.75f)
              <<    QVector4D(0.5f, -0.5f, 0.0f, 0.75f)
              <<    QVector4D(0.5f, 0.5f, 0.0f, 1.5f);
    QVector<QVector2D> texCoords;
    texCoords.reserve(4);
    texCoords <<    QVector2D(0.0f, 0.0f)
              <<    QVector2D(0.0f, 1.0f)
              <<    QVector2D(1.0f, 1.0f)
              <<    QVector2D(1.0f, 0.0f);
//    GLfloat vVertices[] = { -0.5f,  0.5f, -0.0f, 1.5f,  // Position 0
//                                0.0f,  0.0f,              // TexCoord 0
//                               -0.5f, -0.5f, 0.0f, 0.75f, // Position 1
//                                0.0f,  1.0f,              // TexCoord 1
//                                0.5f, -0.5f, 0.0f, 0.75f, // Position 2
//                                1.0f,  1.0f,              // TexCoord 2
//                                0.5f,  0.5f, -0.0f, 1.5f,  // Position 3
//                                1.0f,  0.0f               // TexCoord 3
//                             };
    GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
//    GLushort indices[] = { 0, 3, 2, 0, 2, 1 };
    // Set the viewport
    glViewport(0, 0, width(), height());

    // Clear the color buffer
    glClear(GL_COLOR_BUFFER_BIT);

    // Use the program object
    _userObject.programObject()->bind();

    // Load the vertex position
    _userObject.programObject()->setAttributeArray(_userObject.positionLocation(), vertexCoords.constData());
//    _userObject.programObject()->setAttributeArray(_userObject.positionLocation(), GL_FLOAT, vVertices, 4, 6 * sizeof(GL_FLOAT));

    // Load the texture coordinate
    _userObject.programObject()->setAttributeArray(_userObject.texCoordLocation(), texCoords.constData());
//    _userObject.programObject()->setAttributeArray(_userObject.texCoordLocation(), GL_FLOAT, &vVertices[4], 2, 6 * sizeof(GL_FLOAT));

    _userObject.programObject()->enableAttributeArray(_userObject.positionLocation());
    _userObject.programObject()->enableAttributeArray(_userObject.texCoordLocation());

    // Bind the texture
    _userObject.texture()->activate(0);
    _userObject.texture()->bind();

    // Set the sampler texture unit to 0
    _userObject.programObject()->setUniformValue(_userObject.samplerLocation(), 0);

    QMatrix4x4 m;
    m.ortho(-0.5f, 0.5f, 0.5f, -0.5f, 4.0f, 15.0f);
    m.scale(0.3);
    m.translate(0.0f, 0.0f, -15.0f);
//    m.rotate(135.0f, 1.0f, 0.0f, 0.0f);
    _userObject.programObject()->setUniformValue(_userObject.mvpLocation(), m);

    // Draw quad with nearest sampling
    _userObject.texture()->setMinificationFilter(GLTexture::MinificationNearest);
    _userObject.programObject()->setUniformValue(_userObject.offsetLocation(), -0.6f);
    glDrawElements ( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices );

    // Draw quad with trilinear filtering
    _userObject.texture()->setMinificationFilter(GLTexture::MinificationLinearMipMapLinear);
    _userObject.programObject()->setUniformValue(_userObject.offsetLocation(), 0.6f);
    glDrawElements ( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices );
}
